// JSON reformatting

var addJsonIndent = function (s) {
    s.addEventListener('click', function () {
        var json, es;
        try { json = JSON.parse(s.innerHTML); } catch (e) {
            es = document.createElement('span');
            es.className = 'label label-danger';
            es.appendChild(document.createTextNode('JSON 璇硶閿欒'));
            s.parentNode.insertBefore(es, s.parentNode.nextSibling);
        }
        var samp = document.createElement('pre');
        samp.appendChild(document.createTextNode(JSON.stringify(json, null, 4)));
        s.parentNode.replaceChild(samp, s);
        samp.addEventListener('click', function () {
            samp.parentNode.replaceChild(s, samp);
        });
    });
};

// load XML for JavaScript use

var loadApiXmlAgain = function (callback) {
    var xmlhttp = new window.XMLHttpRequest();
    xmlhttp.open('GET', 'api.xml');
    xmlhttp.onload = function () {
        var xmlDoc = xmlhttp.responseXML;
        if (xmlDoc.documentElement.tagName !== 'api-def') { window.alert('Error parsing api.xml.'); return; }
        callback(xmlDoc);
    };
    xmlhttp.send(null);
};

// validating response JSON against API definition

var validateJsonSchema = function (jsonStr, xmlDoc, apiName) {
    try {
        var xPath = function (xpath, context) { return xmlDoc.evaluate(xpath, context, null, XPathResult.ANY_TYPE, null); };
        var responseObj;
        responseObj = JSON.parse(jsonStr.replace(/[\x00-\x1F\x7F-\x9F]/gi, ''));
        var recursiveValidate = function (xR, obj, debugParentName) {
            var validateSingle = function (expectedType, actualValue, debugName) {
                var butNot = function (shouldBe) {
                    throw debugName + ' 搴斿綋涓� ' + shouldBe +  '锛岃€屼笉鏄� ' + JSON.stringify(actualValue);
                };
                if (expectedType.substr(expectedType.length - 2, 2) === '[]') { // array
                    if (!(actualValue instanceof Array)) {
                        butNot('鏁扮粍');
                    }
                    // recursively check each
                    actualValue.forEach(function (single) {
                        validateSingle(expectedType.substr(0, expectedType.length - 2), single, debugName + ' 鏁扮粍涓殑鍊�');
                    });
                }
                if (expectedType === 'int' || expectedType === 'float') {
                    if (typeof actualValue !== 'number') { butNot('number'); }
                }
                if (expectedType === 'int') {
                    if (Math.round(actualValue) !== actualValue) { butNot('鏁存暟'); }
                }
                if (expectedType === 'boolean') {
                    if (typeof actualValue !== 'boolean') { butNot('boolean'); }
                }
                if (expectedType === 'string') {
                    if (typeof actualValue !== 'string') { butNot('瀛楃涓�'); }
                }
                var childTypeXR = xPath('/api-def/types/class-type[@name=\'' + expectedType + '\']', xmlDoc).iterateNext();
                if (!childTypeXR) { // not a defined class-type, skip checking
                    return;
                }
                if (typeof actualValue !== 'object') {
                    butNot(expectedType + ' 瀵硅薄');
                }
                recursiveValidate(xPath('field', childTypeXR), actualValue, expectedType);
            };
            var expectedName, expectedType, field;
            while (field = xR.iterateNext()) {
                expectedName = field.getAttribute('name');
                expectedType = field.getAttribute('type');
                if (!(expectedName in obj)) {
                    throw debugParentName + ' 涓己灏� ' + expectedName + ' 閿�';
                }
                validateSingle(expectedType, obj[expectedName], debugParentName + ' 涓殑 ' + expectedName);
            }
        };
        var responseXR = xPath('/api-def/apis/api[@name=\'' + apiName + '\']/response/field', xmlDoc);
        if (!('errorCode' in responseObj)) { throw 'API 杩斿洖鍊间腑娌℃湁 errorCode'; }
        if (!('values' in responseObj)) { throw 'API 杩斿洖鍊间腑娌℃湁 values'; }
        recursiveValidate(responseXR, responseObj.values, 'values');
        return null;
    } catch (e) {
        return e;
    }
};

var apiXmlLoaded = function () {

    // hovercard

    var targetDict = {};
    Array.prototype.forEach.call(document.querySelectorAll('div[id^="type-"]'), function (target) {
        targetDict[target.getAttribute('id')] = target;
    });
    Array.prototype.forEach.call(document.querySelectorAll('a[href^="#type-"]'), function (anchor) {
        var added = false;
        if (window.getComputedStyle(anchor.parentNode).position === 'static') {
            anchor.parentNode.style.position = 'relative';
        }
        anchor.addEventListener('mouseenter', function () {
            if (added) { return; }
            added = true;
            var card = targetDict[anchor.getAttribute('href').substr(1)];
            if (!card) { return; }
            card = card.cloneNode(true);
            card.removeAttribute('id');
            card.classList.add('hovercard');
            anchor.parentNode.appendChild(card);
            card.style.left = anchor.offsetLeft + anchor.offsetWidth / 2 - 200 + 'px';
            card.style.top = anchor.offsetTop + anchor.offsetHeight + 8 + 'px';
            var remove = function () {
                anchor.parentNode.removeEventListener('mouseleave', remove);
                anchor.parentNode.removeChild(card);
                added = false;
            };
            anchor.parentNode.addEventListener('mouseleave', remove);
        });
    });

    // error detection

    var alertDiv = document.getElementById('alert');

    var alertDisplayed = (function () {
        var dangers = document.getElementsByClassName('label-danger');
        if (dangers.length) {
            alertDiv.style.display = 'block';
            alertDiv.addEventListener('click', function () {
                dangers[0].scrollIntoView();
            });
            return true;
        }
        return false;
    })();

    loadApiXmlAgain(function (xmlDoc) {
        Array.prototype.forEach.call(document.getElementsByTagName('samp'), function (samp) {
            var apiId = samp.dataset.validateAgainst;
            var invalid = validateJsonSchema(samp.textContent, xmlDoc, apiId);
            var errorHint;
            if (invalid) {
                errorHint = document.createElement('div');
                errorHint.className = 'alert alert-danger';
                errorHint.appendChild(document.createTextNode('绀轰緥涓� API 鏂囨。涓嶇锛�'));
                errorHint.appendChild(document.createTextNode(invalid));
                samp.parentNode.insertBefore(errorHint, samp);
                if (!alertDisplayed) {
                    alertDiv.style.display = 'block';
                    alertDiv.addEventListener('click', function () {
                        errorHint.scrollIntoView();
                    });
                    alertDisplayed = true;
                }
            }
            addJsonIndent(samp);
        });
    });

};